use std::borrow::Borrow;
use std::cell::{Ref, RefCell};
use std::cmp::Ordering;
use std::cmp::Ordering::{Greater, Less};
use std::collections::HashMap;
use std::fmt::{Debug, Formatter, Write};
use std::ops::Add;
use std::sync::Mutex;
use std::time::Duration;
use chrono::Local;

pub struct Timing{
    buckets:Mutex<HashMap<i64,TimingBuckets>>,
    cached_sorted_durations:Vec<Duration>,
    last_cached_time:i64,
}

impl Default for Timing {
    fn default() -> Self {
        Timing{
            buckets:Mutex::new(HashMap::new()),
            cached_sorted_durations: vec![],
            last_cached_time: 0
        }
    }
}

impl Timing {
    pub fn new() -> Self {
        Timing{
            buckets:Mutex::new(HashMap::new()),
            cached_sorted_durations: vec![],
            last_cached_time: 0
        }
    }
}

impl Timing {
    pub fn sorted_durations(&mut self) -> &Vec<Duration> {
        let t = self.last_cached_time.clone();
        let now = Local::now();
        if t + Duration::from_secs(1).as_nanos() as i64 > now.timestamp_nanos() as i64 {
            return &self.cached_sorted_durations;
        }

        let mut durations = Vec::new();
        let buckets = self.buckets.lock().unwrap();
        for (k, mut v) in buckets.iter() {
            if k >= &now.timestamp() {
                for i in v.duration.clone() {
                    durations.push(i);
                }
            }
        }
        durations.sort_by(|a,b| ->Ordering {
            if a < b {
                Less
            } else {
                Greater
            }
        });

        self.cached_sorted_durations = durations;
        self.last_cached_time = now.timestamp_nanos();

        return &self.cached_sorted_durations;
    }

    pub fn add(&mut self,duration:Duration) {
        let now = Local::now();
        let mut b = self.buckets.lock().unwrap().clone();
        let res = b.get_mut(&now.timestamp());
        if let Some(data) = res {
            self.buckets.lock().unwrap().get_mut(&now.timestamp()).unwrap().duration.push(duration);
        } else {
            self.buckets.lock().unwrap().insert(now.timestamp(),TimingBuckets{ duration: vec![duration] });
        }

        self.remove_old_buckets();
    }

    pub fn percentile(&mut self,p:f64) -> u32 {
        let sorted = self.sorted_durations().clone();
        let len = sorted.len();
        if len <= 0 {
            return 0u32;
        }

        let pos = self.ordinal(sorted.len() as u32, p) -1;
        (sorted.get(pos as usize).unwrap().as_nanos() / 1000000) as u32
    }

    pub fn mean(&mut self) -> u32 {
        let sorted = self.sorted_durations();
        let mut sum = 0u128;
        for i in sorted {
            sum += i.as_nanos();
        }

        let len = sorted.len();
        if len == 0 { return 0; }

        (sum / len as u128 / 1000000) as u32
    }

    fn remove_old_buckets(&mut self) {
        let now = Local::now();

        for (k,v) in self.buckets.lock().unwrap().clone() {
            if k <= now.timestamp() - 60 {
                self.buckets.lock().unwrap().remove(&k);
            }
        }
    }

    fn ordinal(&self,len:u32,percentile:f64)-> i64 {
        if percentile == 0f64 && len > 0u32 {
            return 1;
        }

        let mut a:f64 = percentile / 100f64;
        a = a * len as f64;
        a = a.ceil();

        a as i64
    }
}

#[derive(Clone,Debug)]
struct TimingBuckets {
    duration:Vec<Duration>,
}



#[cfg(test)]
mod test {
    use std::time::Duration;
    use crate::hystrix::rolling::timing::Timing;

    #[test]
   fn test() {
        let mut t = Timing::new();
        t.add(Duration::from_secs(1));
        t.add(Duration::from_secs(2));
        println!("{}",t.percentile(80f64));
   }
}



